#coding=utf-8
"""
@author : leecrest
@time   : 14-1-29 下午2:52
@brief  : 网络协议打包与解包
"""

import wafer.utils as utils
import wafer.log as log
import struct
import json
import os


"""
协议数据包的格式：
+----------+----------+----------+----------+----------+
| crypt(1) |  len(2)  |  id(2)   |  len(2)  |   body   |
+----------+----------+----------+----------+----------+
1.crypt：1byte，body是否加密，0：未加密，1：加密
2.len：2byte，body的长度
3.id：2byte，协议编号，唯一标示
4.len：2byte，body的长度
5.body：协议的二进制内容
"""

PACKET_HEAD_1       = 1 #加密
PACKET_HEAD_2       = 2 #长度
PACKET_BODY_1       = 2 #协议编号
PACKET_BODY_2       = 2 #协议长度
PACKET_HEAD_LEN		= PACKET_HEAD_1 + PACKET_HEAD_2 #包头的长度
PACKET_BODY_LEN     = PACKET_BODY_1 + PACKET_BODY_2 #协议头的长度
PACKET_MAX_SIZE		= 2 ** (PACKET_BODY_2 * 8)


#数据类型，格式：{"类型名称":(解析格式,长度),}
BASIC_TYPE = {
	"bool": ("?", 1),
    "int8": ("b", 1), "int16": ("h", 2), "int32": ("i", 4), "long": ("l", 8),
	"uint8": ("B", 1), "uint16": ("H", 2), "uint32": ("I", 4), "ulong": ("L", 8),
}



class CProtocolWriter:
	def __init__(self, iProtoID=0):
		self.m_ProtoID = iProtoID
		self.m_Length = 0
		self.m_Data = ""


	def __WriteString__(self, sValue, iCount=1):
		#print "__WriteString__", sValue, iCount
		if not self.m_ProtoID:
			return
		if iCount == 1:
			iLen = len(sValue)
			if self.m_Length + iLen + 1 > PACKET_MAX_SIZE:
				return
			if iLen == 0:
				self.m_Data += struct.pack("B", iLen)
			else:
				self.m_Data += struct.pack("B%ds" % iLen, iLen, sValue)
			self.m_Length += iLen + 1
		else:
			if iCount <= 0:
				iCount = len(sValue)
				if not self.__WriteNumber__("uint8", iCount):
					return
			for i in xrange(iCount):
				if not self.__WriteString__(sValue[i]):
					return
		return True


	def __WriteNumber__(self, sType, sValue, iCount=1):
		#print "__WriteNumber__", sType, sValue, iCount
		if not self.m_ProtoID or not sType in BASIC_TYPE:
			return
		if iCount == 1:
			sFormat, iLen = BASIC_TYPE[sType]
			if self.m_Length + iLen > PACKET_MAX_SIZE:
				return
			self.m_Data += struct.pack(sFormat, sValue)
			self.m_Length += iLen
		else:
			if iCount <= 0:
				iCount = len(sValue)
				if not self.__WriteNumber__("uint8", iCount):
					return
			for i in xrange(iCount):
				if not self.__WriteNumber__(sType, sValue[i]):
					return
		return True


	def Pack(self):
		if not self.m_ProtoID:
			return
		data = struct.pack("HH", self.m_ProtoID, self.m_Length) + self.m_Data
		self.m_ProtoID = 0
		self.m_Length = 0
		self.m_Data = ""
		return data



class CProtocolReader:
	def __init__(self, data):
		self.m_Offset = 0
		self.m_Data = data
		self.m_Length = len(data)


	#读取字符串类型
	def __ReadString__(self, iCount=1):
		if iCount == 1:
			iValueLen = self.__ReadNumber__("uint8")
			sFormat = "%ds" % iValueLen
			if self.m_Offset + iValueLen > self.m_Length:
				return
			sValue = struct.unpack_from(sFormat, self.m_Data, self.m_Offset)
			self.m_Offset += iValueLen
			return sValue[0]
		else:
			if iCount <= 0:
				iCount = self.__ReadNumber__("uint8")
				if iCount is None:
					return
			sValueList = []
			for i in xrange(iCount):
				sValue = self.__ReadString__()
				if sValue is None:
					return
				sValueList.append(sValue)
			return tuple(sValueList)


	#读取数字类型
	def __ReadNumber__(self, sType, iCount=1):
		if not sType in BASIC_TYPE:
			return
		if iCount == 1:
			sFormat, iValueLen = BASIC_TYPE[sType]
			if self.m_Offset + iValueLen > self.m_Length:
				return
			value = struct.unpack_from(sFormat, self.m_Data, self.m_Offset)
			self.m_Offset += iValueLen
			return value[0]
		else:
			if iCount <= 0:
				iCount = self.__ReadNumber__("uint8")
				if iCount is None:
					return
			sValueList = []
			for i in xrange(iCount):
				sValue = self.__ReadNumber__(sType)
				if sValue is None:
					return
				sValueList.append(sValue)
			return tuple(sValueList)



if not "g_ProtocolReader" in globals():
	g_ProtocolReader = CProtocolReader("")
	g_ProtocolWriter = CProtocolWriter()
	g_PtoC2S = {"Name2ID":{}, "ID2Name":{}}
	g_PtoS2C = {"Name2ID":{}, "ID2Name":{}}
	g_NetReader = None
	g_NetWriter = None


###########################################################################
#第一种协议定制方式，定义好协议格式，在运行时读取格式进行解析

#以下为对外接口
def InitNetProto(sProtoFile, sNetReader, sNetWriter):
	"""
	网络协议的初始化
	:param sProtoList:协议定义文件
	:param sTypeList:协议中的自定义类型定义文件
	:return:
	"""
	global g_PtoC2S, g_PtoS2C, g_NetReader, g_NetWriter
	if not os.path.exists(sProtoFile) or not os.path.isfile(sProtoFile):
		log.Fatal(u"协议文件 %s 不存在", sProtoFile)
		return
	dPtoConfig = json.load(open(sProtoFile, "r"))
	if not dPtoConfig:
		log.Fatal(u"协议文件 %s 读取失败", sProtoFile)
		return
	for sPtoName, iPtoID in dPtoConfig["Name2ID"].iteritems():
		iPtoID = int(iPtoID)
		if "c2s" in sPtoName:
			g_PtoC2S["Name2ID"][sPtoName] = iPtoID
			g_PtoC2S["ID2Name"][iPtoID] = sPtoName
		elif "s2c" in sPtoName:
			g_PtoS2C["ID2Name"][iPtoID] = sPtoName
			g_PtoS2C["Name2ID"][sPtoName] = iPtoID
	g_NetReader = utils.Import(sNetReader)
	g_NetWriter = utils.Import(sNetWriter)


#解析网络包
def UnpackNet(iConnID, sBuff, bC2S=True):
	if len(sBuff) <= PACKET_HEAD_LEN:
		return
	#解析网络包
	try:
		iCrypt, iPackLen = struct.unpack("=BH", sBuff[:PACKET_HEAD_LEN])
	except Exception, err:
		log.Fatal(str(err))
		return
	if len(sBuff) < PACKET_HEAD_LEN + iPackLen:
		return
	#包体，协议数据部分
	sProtoBuff = sBuff[PACKET_HEAD_LEN : PACKET_HEAD_LEN + iPackLen]
	#解密
	if iCrypt:
		sProtoBuff = Decrypt(iConnID, sProtoBuff)
	#判断数据有效性
	if not sProtoBuff or len(sProtoBuff) <= PACKET_BODY_LEN:
		return
	try:
		iProtoID, iProtoLen = struct.unpack("HH", sProtoBuff[:PACKET_BODY_LEN])
	except Exception, err:
		log.Fatal(str(err))
		return
	if len(sProtoBuff) < PACKET_BODY_LEN + iProtoLen:
		return
	sMetaData = sProtoBuff[PACKET_BODY_LEN : PACKET_BODY_LEN + iProtoLen]
	if bC2S:
		sProtoName = g_PtoC2S["ID2Name"].get(iProtoID, None)
	else:
		sProtoName = g_PtoS2C["ID2Name"].get(iProtoID, None)
	if not sProtoName:
		#本服务器没有此协议的定义，不解包
		return "default", iPackLen, {"ProtoID":iProtoID, "Data":sMetaData}

	#将数据加载到读内存中
	g_ProtocolReader.__init__(sMetaData)
	#按照指定的协议格式进行解包
	func = getattr(g_NetReader, "Read_%s" % sProtoName)
	if not func:
		return
	dProtocol = func()
	return sProtoName, iPackLen, dProtocol


#解析元数据包
def UnpackMetaData(iProtoID, sMetaData, bC2S=True):
	if bC2S:
		sProtoName = g_PtoC2S["ID2Name"].get(iProtoID, None)
	else:
		sProtoName = g_PtoS2C["ID2Name"].get(iProtoID, None)
	if not sProtoName:
		return
	#将数据加载到读内存中
	g_ProtocolReader.__init__(sMetaData)
	#按照指定的协议格式进行解包
	func = getattr(g_NetReader, "Read_%s" % sProtoName)
	if not func:
		return
	dProtocol = func()
	return sProtoName, dProtocol


#打包协议
def PackProto(sProtoName, dProtocol, bS2C=True):
	"""
	压包，将源数据按照iIndex的协议格式进行压包
	:param sProtoName:协议名称
	:param dProtocol:
	:return:
	"""
	#准备
	if bS2C:
		iProtoID = g_PtoS2C["Name2ID"][sProtoName]
	else:
		iProtoID = g_PtoC2S["Name2ID"][sProtoName]
	g_ProtocolWriter.__init__(iProtoID)
	func = getattr(g_NetWriter, "Write_%s" % sProtoName)
	if not func:
		log.Fatal("[PackNet] protocol %d is not supported" % iProtoID)
		return
	func(dProtocol)
	return g_ProtocolWriter.Pack()


#直接打包协议
#def PackData(iProtoID, sBuff):
#	if not sBuff:
#		return
#	return struct.pack("HH", iProtoID, len(sBuff)) + sBuff





def ReadString(bList):
	return g_ProtocolReader.__ReadString__(bList)
def ReadNumber(sType, bList):
	return g_ProtocolReader.__ReadNumber__(sType, bList)
def WriteString(sValue, bList):
	return g_ProtocolWriter.__WriteString__(sValue, bList)
def WriteNumber(sType, sValue, bList):
	return g_ProtocolWriter.__WriteNumber__(sType, sValue, bList)


def Name2ID(sName):
	if sName in g_PtoS2C["Name2ID"]:
		return g_PtoS2C["Name2ID"][sName]
	if sName in g_PtoC2S["Name2ID"]:
		return g_PtoC2S["Name2ID"][sName]


###########################################################################
#打包整个网络包
def PackNet(sBuff, bCrypt, *args):
	if not sBuff:
		return
	if bCrypt and len(args) > 0:
		sBuff = Encrypt(args[0], sBuff)
		if not sBuff:
			return
	return struct.pack("=BH", int(bCrypt), len(sBuff)) + sBuff


#数据加密
def Encrypt(iConnID, sBuff):
	if not iConnID or not sBuff:
		return sBuff
	return sBuff


#数据解密
def Decrypt(iConnID, sBuff):
	if not iConnID or not sBuff:
		return sBuff
	return sBuff



__all__ = ["PACKET_HEAD_LEN", "BASIC_TYPE", "InitNetProto", "Name2ID",
           "PackProto", "PackNet", "UnpackNet", "UnpackMetaData",
           "ReadString", "ReadNumber", "WriteString", "WriteNumber"]